import random
import attr
import logging
import time
import re


logger = logging.getLogger(__name__)


def exp_backoff(attempt, cap=3600, base=300):
    """ Exponential backoff time """
    # this is a numerically stable version of
    # min(cap, base * 2 ** attempt)
    max_attempts = math.log(cap / base, 2)
    if attempt <= max_attempts:
        return base * 2 ** attempt
    return cap

def exp_backoff_full_jitter(*args, **kwargs):
    """ Exponential backoff time with Full Jitter """
    return random.uniform(0, exp_backoff(*args, **kwargs))


@attr.s
class AccountState(object):
    failed_attempts = attr.ib(default=0)
    next_check = attr.ib(default=None)
    backoff_time = attr.ib(default=None)  # for debugging

class Accounts(object):
    def __init__(self, account_list, backoff=None):
        self.accounts = {Account(**parse_account(account)): AccountState() for account in account_list}
        self.unchecked = set(self.accounts.keys())
        self.good = set()
        self.dead = set()

        if backoff is None:
            backoff = exp_backoff_full_jitter
        self.backoff = backoff

    def get_random(self):
        available = list(self.unchecked | self.good)
        if not available:
            return None
        return random.choice(available)

    def mark_dead(self, account, _time=None):
        if account not in self.accounts:
            logger.warn("Account <%s> was not found in proxies list" % account)
            return
        
        if account in self.good:
            logger.debug("GOOD account became DEAD: <%s>" % account)
        else:
            logger.debug("Account <%s> is DEAD" % account)
        
        self.unchecked.discard(account)
        self.good.discard(account)
        self.dead.add(account)
        now = _time or time.time()
        state = self.accounts[account]
        state.backoff_time = self.backoff(state.failed_attempts)
        state.next_check = now + state.backoff_time
        state.failed_attempts += 1
    def mark_good(self, account):
        """ Mark a account as good """
        if account not in self.accounts:
            logger.warn("Account <%s> was not found in account list" % account)
            return

        if account not in self.good:
            logger.debug("Account <%s> is GOOD" % account)

        self.unchecked.discard(account)
        self.dead.discard(account)
        self.good.add(account)
        self.accounts[account].failed_attempts = 0
    def reset(self):
        for account in list(self.dead):
            self.dead.remove(account)
            self.unchecked.add(account)

    def reanimate(self, _time=None):
        """ Move dead proxies to unchecked if a backoff timeout passes """
        n_reanimated = 0
        now = _time or time.time()
        for account in list(self.dead):
            state = self.accounts[account]
            assert state.next_check is not None
            if state.next_check <= now:
                self.dead.remove(account)
                self.unchecked.add(account)
                n_reanimated += 1
        return n_reanimated

    @property
    def mean_backoff_time(self):
        if not self.dead:
            return 0.0
        total_backoff = sum(self.accounts[p].backoff_time for p in self.dead)
        return float(total_backoff) / len(self.dead)


    @property
    def reanimated(self):
        return [p for p in self.unchecked if self.accounts[p].failed_attempts]

    def __str__(self):
        n_reanimated = len(self.reanimated)
        return "Proxies(good: {}, dead: {}, unchecked: {}, reanimated: {}, " \
               "mean backoff time: {}s)".format(
            len(self.good), len(self.dead),
            len(self.unchecked) - n_reanimated, n_reanimated,
            int(self.mean_backoff_time),
        )


@attr.s(hash=True)
class Account(object):
    username = attr.ib()
    password = attr.ib()
    session_id = attr.ib()
    proxy = attr.ib()

def parse_account(account):
    print(account)
    p = re.compile('(?P<username>\w+):(?P<password>.*)@(?P<proxy>.*)\+(?P<session_id>.*)')
    try:
        account_info = re.search(p, account).groupdict()
    except Exception as e:
        print("错误", account)
        raise e
    # session_id = api.get_session_id(f"{username}:{password}")
    # account_info["session_id"] = session_id
    return account_info


def exp_backoff(attempt, cap=3600, base=300):
    """ Exponential backoff time """
    # this is a numerically stable version of
    # min(cap, base * 2 ** attempt)
    max_attempts = math.log(cap / base, 2)
    if attempt <= max_attempts:
        return base * 2 ** attempt
    return cap


def exp_backoff_full_jitter(*args, **kwargs):
    """ Exponential backoff time with Full Jitter """
    return random.uniform(0, exp_backoff(*args, **kwargs))